package ua.org.extjsbuilder.parser.runtime.builder;

import java.io.EOFException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import ua.org.extjsbuilder.parser.IInstruction;
import ua.org.extjsbuilder.parser.JSParseException;
import ua.org.extjsbuilder.parser.runtime.UnparsedToken;
import ua.org.extjsbuilder.parser.runtime.instructions.BinaryOperator;
import ua.org.extjsbuilder.parser.runtime.instructions.TernaryOperator;
import ua.org.extjsbuilder.parser.runtime.instructions.UnaryOperator;
import ua.org.extjsbuilder.parser.scanner.CharPattern;

abstract class OperatorsInterpreter extends KeywordsInterpreter 
		implements OperatorsStates {
	private static final Map<Character,Integer> OPERATORS_TO_STATES_MAP;
	private static final CharPattern OPERATORS = CharPattern.compile(".+-%/*&^|=!<>,[]?");
	
	static {
		Map<Character,Integer> map = new HashMap<Character, Integer>();
		map.put('(', OPEN_FUNCTION_CALL);
		map.put('.', ACCESS_PROPERTY);
		map.put('+', OPEN_PLUS);
		map.put('-', OPEN_MINUS);
		map.put('%', OPEN_MOD);
		map.put('/', OPEN_DIV);
		map.put('*', OPEN_MUL);
		map.put('&', OPEN_AND);
		map.put('^', OPEN_XOR);
		map.put('|', OPEN_OR);
		map.put('=', OPEN_EQ);
		map.put('!', OPEN_NOT);
		map.put('>', OPEN_GT);
		map.put('<', OPEN_LT);
		map.put(',', COMA);
		map.put('[', OPEN_INDEX);
		map.put(']', CLOSE_INDEX);
		map.put('?', IF_THEN_ELSE);
		OPERATORS_TO_STATES_MAP = Collections.unmodifiableMap(map);
	}

	OperatorsInterpreter(UnparsedToken start) {
		super(start);
	}

	boolean next() throws EOFException{
		super.next();
		analyzeOperator();
		return true;
	}

	void analyzeOperator() throws EOFException {
		if(data.length<=pos)nextChar();
		if(data[pos]==')')return;
		if(EXPECT_OPERATOR==state){
			if(CharPattern.ALLOW_WHITE_SPACES
					.match(data[pos])){
				return;
			}
			getFirstRightState();
		}
		switch(state){
		case OPEN_FUNCTION_CALL:
			parseFunctionCall();
			break;
		case ACCESS_PROPERTY:
			parseAccessProperty();
			break;
		case OPEN_PLUS:
			if(isNextEQ())parseXEq(BinaryOperator.PLUS_EQ);
			else if(isNextTheSame())parseIncrement();
			else parseOperator(BinaryOperator.PLUS,"Unexpected + operator");
			break;
		case OPEN_MINUS:
			if(isNextEQ())parseXEq(BinaryOperator.MINUS_EQ);
			else if(isNextTheSame())parseDecrement();
			else parseOperator(BinaryOperator.MINUS,"Unexpected - operator");
			break;
		case OPEN_MOD:
			if(isNextEQ())parseXEq(BinaryOperator.MOD_EQ);
			else err("Unexpected % in left value");
			break;
		case OPEN_DIV:
			if(isNextEQ())parseXEq(BinaryOperator.DIV_EQ);
			else parseOperator(BinaryOperator.DIVIDE,"Unexpected / operator");
			break;
		case OPEN_MUL:
			if(isNextEQ())parseXEq(BinaryOperator.MUL_EQ);
			else parseOperator(BinaryOperator.MULTIPLY,"Unexpected * operator");;
			break;
		case OPEN_AND:
			if(isNextEQ())parseXEq(BinaryOperator.AND_EQ);
			else if(isNextTheSame())parseBooleanExpression(BinaryOperator.CONDITIONAL_AND);
			else parseBooleanExpression(BinaryOperator.AND);
			break;
		case OPEN_XOR:
			if(isNextEQ())parseXEq(BinaryOperator.XOR_EQ);
			else parseBooleanExpression(BinaryOperator.XOR);
			break;
		case OPEN_OR:
			if(isNextEQ())parseXEq(BinaryOperator.OR_EQ);
			else if(isNextTheSame())parseBooleanExpression(BinaryOperator.CONDITIONAL_OR);
			else parseBooleanExpression(BinaryOperator.OR);
			break;
		case OPEN_EQ:
			if(isNextEQ()&&isThirdEq())parseBooleanExpression(BinaryOperator.MATCH);
			else if(isNextEQ())parseBooleanExpression(BinaryOperator.COMPARE);
			else parseXEq(BinaryOperator.EQ);
			break;
		case OPEN_NOT:
			if(isNextEQ()&&isThirdEq())parseBooleanExpression(BinaryOperator.MATCH_NOT);
			else if(isNextEQ())parseBooleanExpression(BinaryOperator.COMPARE_NOT);
			else err("Unexpected NOT in left value");
			break;
		case OPEN_GT:
			if(isShiftRightEq())parseBooleanExpression(BinaryOperator.GT_EQ);
			else parseBooleanExpression(BinaryOperator.GT);
			break;
		case OPEN_LT:
			if(isShiftLeftEq())parseBooleanExpression(BinaryOperator.LT_EQ);
			else parseBooleanExpression(BinaryOperator.LT);
			break;
		case COMA:
			parseComa();
			break;
		case OPEN_INDEX:
			parseOpenIndex();
			break;
		case CLOSE_INDEX:
			err("Unexpected closing bracket");
			break;
		case IF_THEN_ELSE:
			parseIfThenElse();
			break;
		}
	}

	protected boolean isOperatorChain() throws EOFException{
		//if(isNextTheSame())return false;TODO:check for increment and decrement
		
		return match(OPERATORS);
	}
	
	private void parseOperator(String type,String errMsg){
		ExpressionsInterpreter intr = new ExpressionsInterpreter(token, pos+1);
		if(intr.isLine()&&!intr.match(CharPattern.ALLOW_WHITE_SPACES)){
			intr.pos--;
		}
		if(previous==null)err(errMsg);
		lastComplete = new BinaryOperator(type,
				previous,intr.buildExpression());
		setState(intr);
		state = LEFT_START;
	}

	private void parseIfThenElse() throws EOFException {
		ExpressionsInterpreter interpreter = 
					new ExpressionsInterpreter(token, pos);
		IInstruction thenInstruction = interpreter
					.buildExpression();
		interpreter.skipWhiteSpaces();
		if(data[pos]!=':'){
			interpreter.err("Colon expected to compete ternar operator");
		}
		IInstruction elseInstruction = interpreter
				.buildExpression();
		setState(interpreter);
		assertEnd();
		lastComplete = new TernaryOperator(previous,
				thenInstruction,elseInstruction);
		state = LEFT_START;
	}

	private void parseOpenIndex() throws EOFException {
		pos++;
		ExpressionsInterpreter interpreter = 
			new ExpressionsInterpreter(token, pos);
		IInstruction indexExpression = interpreter
				.buildExpression();
		previous = new BinaryOperator(BinaryOperator.INDEX,
				previous,indexExpression);
		setState(interpreter);
		skipWhiteSpaces();
		if(data[pos]!=']'){
			err("Missing closing square bracket");
		}
		else {
			pos++;
		}
		state = LEFT_END;
	}

	private void parseComa() throws EOFException {
		List<IInstruction> result = new LinkedList<IInstruction>();
		result.add(previous);
		ExpressionsInterpreter interpreter = new ExpressionsInterpreter(token, pos);
		while(interpreter.data[interpreter.pos]==','){
			interpreter.pos++;
			result.add(interpreter.buildExpression());
			interpreter.skipWhiteSpaces();
		}
		setState(interpreter);
		assertEnd();
		state = LEFT_START;
	}

	private void parseBooleanExpression(String type) {
		previous = new BinaryOperator(type,
				previous,new ExpressionsInterpreter(token, pos)
					.buildExpression());
		state = LEFT_START;
	}

	private void parseXEq(String type) throws EOFException {
		ExpressionsInterpreter intr = new ExpressionsInterpreter(token, pos+1);
		lastComplete = new BinaryOperator(type,
				previous,intr.buildExpression());
		setState(intr);
		state = LEFT_START;
	}

	private void parseDecrement() throws EOFException {
		previous = new UnaryOperator(UnaryOperator.DECREMENT,previous);
		nextChar();nextChar();
		state = LEFT_START;
	}

	private void parseIncrement() throws EOFException {
		previous = new UnaryOperator(UnaryOperator.INCREMENT,previous);
		nextChar();nextChar();
		state = LEFT_START;
	}

	private boolean isShiftLeftEq() {
		if(data.length>(pos+2)&&
				data[pos+1]=='>'&&
				data[pos+2]=='='){
			return true;
		}
		if(data.length>(pos+3)&&
				data[pos+1]=='>'&&
				data[pos+2]=='>'&&
				data[pos+3]=='='){
			return true;
		}
		return false;
	}

	private boolean isShiftRightEq() {
		if(data.length>(pos+2)&&
				data[pos+1]=='<'&&
				data[pos+2]=='='){
			return true;
		}
		return false;
	}

	protected abstract void parseAccessProperty() throws EOFException;

	protected abstract void parseFunctionCall() throws EOFException;

	private boolean isNextTheSame() throws EOFException {
		if(data.length>(pos+1)){
			return data[pos]==data[pos+1];
		}
		return isNextTheSameComplex();
	}

	private boolean isNextTheSameComplex() throws EOFException {
		char fst,sec;
		ExpressionsInterpreter intr = new ExpressionsInterpreter(token,pos);
		if(intr.isNextLiteral()){
			return false;
		}
		if(data.length>pos){
			fst = data[pos];
			sec = intr.nextChar();
		}
		else {
			fst = intr.nextChar();
			sec = intr.nextChar();
		}
		return fst==sec;
	}

	private boolean isNextEQ() {
		return data.length>(pos+1)&&data[pos+1]=='=';
	}
	
	private boolean isThirdEq() {
		return data.length>(pos+2)&&data[pos+2]=='=';
	}

	private void getFirstRightState() throws EOFException {
		if(!OPERATORS_TO_STATES_MAP.containsKey(data[pos])
				&&fixLastPosition()){
			throw new JSParseException("Unknown operator '"+
					data[pos]+'\'', token.getLine());
		}
		state = OPERATORS_TO_STATES_MAP.get(data[pos]);
	}

	private boolean fixLastPosition() throws EOFException {
		if(pos+1!=data.length)return true;
		nextChar();
		return false;
	}
}
